#define GL_GLEXT_PROTOTYPES
#include "shaderExample.hpp"
#include "CubeBox.hpp"
#include <iostream>
#include <QtOpenGL>
#include "PolyList.hpp"

static int GridLightDim = 1;
static float GridLightSize = 0.05;
static int ShadowResolution = 128;
static float sizeCubebox = 5;
static int KernelConvolution = 1;

ShaderExample::ShaderExample()
{
	shadowSize.setWidth(ShadowResolution);
	shadowSize.setHeight(ShadowResolution);
}

void
ShaderExample::draw()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    drawShadowMaps();
    drawSceneWithShadow();
    drawLights();

	if(GridLightDim <= 1 )
        drawMasterTexture();
}

void
ShaderExample::init()
{
    glClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
    glDisable(GL_CULL_FACE);
    glEnable(GL_DEPTH_TEST);
    initMatrices();
    initShader();
    initFramebuffer();
    cubebox = CubeBox(sizeCubebox);
}

void
ShaderExample::initMatrices()
{
    view.setPerspective(70.0, 1.0, 1., 100.0);
    view.lookAt(QVector3D( 0, 1.0, 8.0), QVector3D(0.0, 0.0, 0.0), QVector3D(0.0, 1.0, 0.0));

    mainLight.setPerspective(90.0, 1.0, 1., 100.0);
    mainLight.lookAt(QVector3D(2.0, 2.0, 2.0), QVector3D(0.0, 0.0, 0.0), QVector3D(0.0, 1.0, 0.0));

	std::vector<QVector3D> positions = jitteringLight(mainLight, GridLightSize, GridLightDim);
    for(size_t i = 0; i <  positions.size(); i++)
    {
        Light self;
        self.setPerspective(90.0, 1.0, 1.0, 100.0);
        self.lookAt(positions.at(i), mainLight.getTarget() + mainLight.getEye() - positions.at(i), mainLight.getUp());
        subLights.push_back(self);
    }

	/*
	PolyList p(verticesTab, indicesTab, sizeof(indicesTab)/sizeof(int)/3);
    p.addPolygons(cubebox.getVertices(), cubebox.getIndices(), CUBEBOX_NB_POLY);
    for(size_t i = 0; i < subLights.size(); ++i)
    {
        vplList.push_back(generateVPL(subLights.at(i), p));
	}
	*/
}

void
ShaderExample::initFramebuffer()
{
    glEnable(GL_TEXTURE_2D);

    //shadowRender
    glGenTextures( 1, &shadowTexture );
    glBindTexture( GL_TEXTURE_2D, shadowTexture );
    glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, shadowSize.width()* subLights.size(), shadowSize.height(), 0, GL_DEPTH_COMPONENT, GL_FLOAT, 0 );

    glGenFramebuffers( 1, &shadowFramebuffer );
    glBindFramebuffer( GL_DRAW_FRAMEBUFFER, shadowFramebuffer );
    glFramebufferTexture2D( GL_DRAW_FRAMEBUFFER,  GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, shadowTexture, 0 );

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
    glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );

    glBindFramebuffer( GL_DRAW_FRAMEBUFFER, 0 );

    //ShadowMapsFBO
    glGenTextures( 1, &masterTexture );
    glBindTexture( GL_TEXTURE_2D, masterTexture );
    glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, shadowSize.width()* subLights.size(), shadowSize.height(), 0, GL_DEPTH_COMPONENT, GL_FLOAT, 0 );

    glGenFramebuffers( 1, &masterFramebuffer );
    glBindFramebuffer( GL_DRAW_FRAMEBUFFER, masterFramebuffer );
    glFramebufferTexture2D( GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, masterTexture, 0 );
    glBindFramebuffer( GL_DRAW_FRAMEBUFFER, 0 );

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
    glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );

    glBindFramebuffer( GL_DRAW_FRAMEBUFFER, 0 );
}

void
ShaderExample::initShader()
{
	shadowProgramShader = createShader((char*)":/shader/Shadow.vert", NULL);
	convolutionProgramShader = createShader(NULL, (char*)":/shader/Convolution.frag");
	sceneRenderProgramShader = createShader((char*)":/shader/renderScene.vert", (char*)":/shader/renderScene.frag");
}

void ShaderExample::drawShadowMaps()
{
    shadowProgramShader->bind();
    glBindFramebuffer( GL_DRAW_FRAMEBUFFER, masterFramebuffer );
    glClear(GL_DEPTH_BUFFER_BIT);
    for(size_t i = 0; i <  subLights.size(); i++)
    {
        glViewport(shadowSize.width() * i, 0, shadowSize.width(), shadowSize.height());
        shadowProgramShader->setUniformValue("MVPMatrix", subLights.at(i).getPerspective()*subLights.at(i).getProjection());
		shadowProgramShader->setUniformValue("resolution", ShadowResolution);

        drawSceneSample();
    }
    glBindFramebuffer( GL_DRAW_FRAMEBUFFER, 0 );
    shadowProgramShader->release();

    /**/
    convolutionProgramShader->bind();
    glBindFramebuffer( GL_DRAW_FRAMEBUFFER, shadowFramebuffer );
    glClear(GL_DEPTH_BUFFER_BIT);
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, masterTexture);
    convolutionProgramShader->setUniformValue("Width", (int)(shadowSize.width()* subLights.size()));
    convolutionProgramShader->setUniformValue("Height",shadowSize.height());
    convolutionProgramShader->setUniformValue("Shadow", 0);
	convolutionProgramShader->setUniformValue("KernelSize", KernelConvolution);
    glViewport(0, 0, shadowSize.width() * subLights.size(), shadowSize.height());
    glMatrixMode(GL_PROJECTION);
    QMatrix4x4 mat;
    float ratio = (float)(shadowSize.width()* subLights.size()) / shadowSize.height();
    mat.perspective(90, ratio, 1.0, 100.0);
    glLoadMatrixd(mat.data());
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glBegin(GL_QUADS);
    glVertex3f(-1.0 * subLights.size(), 1.0, -1.0);
    glVertex3f( 1.0 * subLights.size(), 1.0, -1.0);
    glVertex3f( 1.0 * subLights.size(),-1.0, -1.0);
    glVertex3f(-1.0 * subLights.size(),-1.0, -1.0);
    glEnd();
    glBindFramebuffer( GL_DRAW_FRAMEBUFFER, 0 );
    convolutionProgramShader->release();
    /**/
}

void ShaderExample::drawSceneWithShadow()
{
    glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
    glViewport(0, 0, viewSize.width(), viewSize.height());
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, shadowTexture);
    QMatrix4x4 lights[subLights.size()];
    for(size_t i = 0; i < subLights.size(); i++)
    {
        lights[i] = getBiasMatrix()*subLights[i].getPerspective()*subLights[i].getProjection();
    }
    sceneRenderProgramShader->bind();
    sceneRenderProgramShader->setUniformValue("MVPMatrix", view.getPerspective()*view.getProjection());
    sceneRenderProgramShader->setUniformValue("nbLight", (int) subLights.size());
    sceneRenderProgramShader->setUniformValueArray("MCtoLightMatrix", lights, subLights.size());
    sceneRenderProgramShader->setUniformValue("ShadowMap", 0);
    sceneRenderProgramShader->setUniformValue("lightPos", mainLight.getEye());

    drawScene();

    sceneRenderProgramShader->release();
    glBindFramebuffer( GL_DRAW_FRAMEBUFFER, 0 );
}

void ShaderExample::drawScene()
{
    QMatrix4x4 ModelView;
    ModelView.setToIdentity();
    sceneRenderProgramShader->setUniformValue("ModelView", ModelView);
    cubebox.drawCubeBox();

    QList<Model3D>::iterator i;
    for (i = modelList.begin(); i != modelList.end(); ++i)
    {
        sceneRenderProgramShader->setUniformValue("ModelView", i->getModelView());
        i->draw();
    }
}

void ShaderExample::drawSceneSample()
{
    QList<Model3D>::iterator it;
    for (it = modelList.begin(); it != modelList.end(); ++it)
    {
        shadowProgramShader->setUniformValue("ModelView", it->getModelView());
        it->drawSample();
    }
}

void ShaderExample::drawLights()
{
    glMatrixMode(GL_PROJECTION);
    glLoadMatrixd(view.getPerspective().data());
    glMatrixMode(GL_MODELVIEW);
    for(size_t i = 0; i <  subLights.size(); i++){
        glLoadMatrixd(view.getProjection().data());
        glColor3f(1.0, 1.0, 0.0);
        QVector3D trans = subLights.at(i).getEye();
        glTranslated(trans.x(), trans.y(), trans.z());
        GLUquadricObj *quadric=gluNewQuadric();
        gluQuadricNormals(quadric, GLU_SMOOTH);
        gluSphere(quadric, 0.05, 10, 10);
        gluDeleteQuadric(quadric);
    }
	//drawVPLs();
}

void ShaderExample::drawVPLs()
{
    for(size_t i = 0; i < vplList.size();++i)
    {
        glLoadMatrixd(view.getProjection().data());
        glColor3f(0.0, 0.0, 1.0);
        QVector3D trans = vplList.at(i).getEye();
        glTranslated(trans.x(), trans.y(), trans.z());
        GLUquadricObj *quadric=gluNewQuadric();
        gluQuadricNormals(quadric, GLU_SMOOTH);
        gluSphere(quadric, 0.05, 5, 5);
        gluDeleteQuadric(quadric);
    }
}

void ShaderExample::drawMasterTexture()
{
    glClear(GL_DEPTH_BUFFER_BIT );
    glLoadIdentity();
	glTranslatef(3.0,3.0,-6.0);
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, masterTexture);
    glBegin(GL_QUADS);
    glColor3f(1.0 * subLights.size(), 1.0, 1.0);
    glTexCoord2f(0.0,1.0);
    glVertex3f(-1.0 * subLights.size() , 1.0, 0.0);
    glTexCoord2f(1.0,1.0);
    glVertex3f( 1.0 * subLights.size(), 1.0, 0.0);
    glTexCoord2f(1.0,0.0);
    glVertex3f( 1.0 * subLights.size(),-1.0, 0.0);
    glTexCoord2f(0.0,0.0);
    glVertex3f(-1.0 * subLights.size(),-1.0, 0.0);
    glEnd();
    glDisable(GL_TEXTURE_2D);

    glClear(GL_DEPTH_BUFFER_BIT );
    glLoadIdentity();
	glTranslatef(3.0,-3.0,-6.0);
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, shadowTexture);
    glBegin(GL_QUADS);
    glColor3f(1.0, 1.0, 1.0);
    glTexCoord2f(0.0,1.0);
    glVertex3f(-1.0 * subLights.size(), 1.0, 0.0);
    glTexCoord2f(1.0,1.0);
    glVertex3f( 1.0 * subLights.size(), 1.0, 0.0);
    glTexCoord2f(1.0,0.0);
    glVertex3f( 1.0 * subLights.size(),-1.0, 0.0);
    glTexCoord2f(0.0,0.0);
    glVertex3f(-1.0 * subLights.size(),-1.0, 0.0);
    glEnd();
    glDisable(GL_TEXTURE_2D);
}
